/**
 * Copyright (c) 2020 lizebang
 *
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

package cmdutil

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
)

type CmdError struct {
	Root        string
	Cmd         string
	Args        []string
	ErrorOutput string
	err         error
}

func Error(errorOutput string, err error, root, cmd string, args ...string) CmdError {
	return CmdError{
		Root:        root,
		Cmd:         cmd,
		Args:        args,
		ErrorOutput: errorOutput,
		err:         err,
	}
}

func (ce CmdError) Error() string {
	var result string
	if result == "" {
		result = "(?)"
	} else {
		result = fmt.Sprintf("(%s)", ce.Root)
	}
	result += fmt.Sprintf("'%s %s' ", ce.Cmd, strings.Join(ce.Args, " "))
	result += "failed:\n"
	result += "stderr:\n"
	result += ce.ErrorOutput
	result += "\ncommand fail error: " + ce.err.Error()
	return result
}

func Run(root, cmd string, args ...string) error {
	return run(root, cmd, args...)
}

func run(root, cmd string, args ...string) error {
	var stdout, stderr bytes.Buffer
	if err := runCmd(&stdout, &stderr, root, cmd, args...); err != nil {
		return err
	}
	return nil
}

func RunOutputOne(root, cmd string, args ...string) (string, error) {
	return runOutputOne(root, cmd, args...)
}

func runOutputOne(root, cmd string, args ...string) (string, error) {
	var stdout, stderr bytes.Buffer
	if err := runCmd(&stdout, &stderr, root, cmd, args...); err != nil {
		return "", err
	}
	return strings.TrimSuffix(stdout.String(), "\n"), nil
}

func trimOutput(o string) []string {
	output := strings.TrimSpace(o)
	if len(output) == 0 {
		return nil
	}
	return strings.Split(output, "\n")
}

func RunOutput(root, cmd string, args ...string) ([]string, error) {
	return runOutput(root, cmd, args...)
}

func runOutput(root, cmd string, args ...string) ([]string, error) {
	var stdout, stderr bytes.Buffer
	if err := runCmd(&stdout, &stderr, root, cmd, args...); err != nil {
		return nil, err
	}
	return trimOutput(stdout.String()), nil
}

func RunInteractive(root, cmd string, args ...string) error {
	return runInteractive(root, cmd, args...)
}

func runInteractive(root, cmd string, args ...string) error {
	var stderr bytes.Buffer
	// In order for the editing to work correctly with
	// terminal-based editors, notably "vim", use os.Stdout.
	if err := runCmd(os.Stdout, &stderr, root, cmd, args...); err != nil {
		return err
	}
	return nil
}

func RunCmd(stdout, stderr io.Writer, root, cmd string, args ...string) error {
	return runCmd(stdout, stderr, root, cmd, args...)
}

func runCmd(stdout, stderr io.Writer, root, cmd string, args ...string) error {
	var outbuf bytes.Buffer
	var errbuf bytes.Buffer
	command := exec.Command(cmd, args...)
	if root != "" {
		command.Dir = root
	}
	command.Stdin = os.Stdin
	command.Stdout = io.MultiWriter(stdout, &outbuf)
	command.Stderr = io.MultiWriter(stderr, &errbuf)
	err := command.Run()
	if err != nil {
		return Error(errbuf.String(), err, root, cmd, args...)
	}
	return nil
}
